#include "sudoku.h"
#include <fstream>
#include <QList>
#include <QDebug>
#include <iostream>

using namespace std ;

//Создаёт объект судоку из списка больших блоков
int Sudoku::transactionCounter = 0 ;
QVector<PartitionElement*> Sudoku::partitionVector ;

Sudoku::~Sudoku()
{
    foreach( BigBlock* bigBlock, bigBlockList )
    {
        delete bigBlock ;
    }

    //Delete matrix with small elements
    for( int i=0 ; i<xMax ; ++i )
        delete [] matrix[i] ;
    delete [] matrix ;


}

int Sudoku::getAllElements()
{
    int allElementCounter = 0 ;
    for( int i=0 ; i<xMax ; ++i )
    {
        for( int j=0 ; j<yMax ; ++j )
        {
            if( 0 != matrix[ i ][ j ] )
            {
                allElementCounter += 9 ;
            }
        }
    }

    return allElementCounter ;

}

int Sudoku::getBusyElements()
{
    int busyElementCounter = 0 ;
    for( int i=0 ; i<xMax ; ++i )
    {
        for( int j=0 ; j<yMax ; ++j )
        {
            if( 0 != matrix[ i ][ j ] )
            {
                busyElementCounter += matrix[ i ][ j ]->getBusyElements() ;
            }
        }
    }

    return busyElementCounter ;
}


/*Возможности тупика:
 *Есть элемент со значение 0 и без вариантов
 *Все клетки заполнены
 **/
bool Sudoku::isDeadlock()
{
    bool cellWithNullIsIt = false ;
    foreach( BigBlock* bigBlock, bigBlockList)
    {
        for( int row=0 ; row<3 ; ++row )
        {
            for( int column=0 ; column<3 ; ++column )
            {
                SmallBlock* smallBlock = bigBlock->getSmallBlock( row, column ) ;
                for( int i=0 ; i<3 ; ++i )
                {
                    for( int j=0 ; j<3 ; ++j )
                    {
                        Cell* cell = smallBlock->getCell( i, j ) ;
                        if( 0 != cell->getValue() )
                        {
                            continue ;
                        }
                        cellWithNullIsIt = true ;

                        Assumption assum = cell->getAssumption() ;
                        bool allIsNull = true ;
                        for( int index=1 ; index<10 ; ++index )
                        {
                            if( Can == assum.getAssum( index ) )
                            {
                                allIsNull = false ;
                            }
                        }

                        if( allIsNull )
                        {
                            return true ;
                        }
                    }
                }
            }
        }

    }
    if( !cellWithNullIsIt )
        return true ;
    return false ;
}



int Sudoku::service()
{

    int counter = 0 ;
    bool assumptionResult = true ;
    bool partitionResult = true ;
    while( assumptionResult || partitionResult )
    {
        assumptionResult = false ;
        partitionResult = false ;

        //Make assumptions for all big blocks
        foreach( BigBlock* bigBlock, bigBlockList )
        {
            int number = bigBlock->makeTheAssumption() ;
            counter += number ;
            if( number > 0 )
            {
                assumptionResult = true ;
            }
        }




        //Find partition element
        PartitionElement* partition = 0 ;

        foreach( BigBlock* bigBlock, bigBlockList )
        {
            partition = bigBlock->getPartitionElement() ;
            if( 0 != partition )
            {
                ++counter ;
                partitionResult = true ;
                //Set value from partition element
                partition->exec() ;
                break ;
            }
        }

    }
    return counter ;
}




int Sudoku::initTransaction()
{
    //cout << "Init new transaction" << endl ;
    return ++transactionCounter ;
}

PartitionElement* Sudoku::makeAssumption()
{
    //Найти наиболее заполненный большой блок
    BigBlock* selectedBigBlock =0 ;
    int maxBusyElementsOfBigBlock = -1 ;
    foreach( BigBlock* bigBLock, bigBlockList )
    {
        int currBusyElements = bigBLock->getBusyElements() ;
        if( 81 != currBusyElements && currBusyElements > maxBusyElementsOfBigBlock )
        {
            maxBusyElementsOfBigBlock = currBusyElements ;
            selectedBigBlock = bigBLock ;
        }
    }

    //Найти наиболее заполненный маленький блок
    SmallBlock* selectedSmallBlock =0 ;
    int maxBusyElementsOfSmallBlock = -1 ;
    for( int row=0 ; row<3 ; ++row )
    {
        for( int column=0 ; column<3 ; ++column )
        {
            SmallBlock* currSmallBlock = selectedBigBlock->getSmallBlock( row, column ) ;
            int currBusyElements = currSmallBlock->getBusyElements() ;
            if( 9 != currBusyElements && currBusyElements > maxBusyElementsOfSmallBlock )
            {
                selectedSmallBlock = currSmallBlock ;
                maxBusyElementsOfSmallBlock = currBusyElements ;
            }
        }
    }

    Cell* selectedCell = 0 ;
    int minAssumptions = 10 ;
    //Найти ячейку с наименьшим количестков предположений
    for( int row=0 ; row<3 ; ++row )
    {
        for( int column=0 ; column<3 ; ++column )
        {
            Cell* currCell = selectedSmallBlock->getCell( row, column ) ;
            if( 0 != currCell->getValue() )
                continue ;
            Assumption currAssum = currCell->getAssumption() ;
            int currAssumptions = 0 ;
            for( int i=1 ; i<10 ; ++i )
            {
                if( Can == currAssum.getAssum( i ) )
                    ++currAssumptions ;
                if( currAssumptions < minAssumptions )
                {
                    minAssumptions = currAssumptions ;
                    selectedCell = currCell ;
                }
            }
        }
    }

    //Сделать предположение
    Assumption assum = selectedCell->getAssumption() ;
    PartitionElement* partitionElement ;
    for( int index=1 ; index<10 ; ++index )
    {
        if( Can == assum.getAssum( index ) )
        {
            partitionElement =
                    new PartitionElement( selectedCell, index ) ;
        }
    }
    return partitionElement ;
}



QList<BigBlock*>* Sudoku::smallBlockToBigBlock()
{
    QList<BigBlock*>* bigBlockList = new QList<BigBlock*>() ;

    for( int row=0 ; row<(xMax-2) ; ++row )
    {
        for( int column=0 ; column<(yMax-2) ; ++column )
        {
            SmallBlock* smallBlockArray[9] ;
            int count = 0 ;
            bool toExit = false ;
            for( int x=row ; x<(row+3) && !toExit ; ++x )
            {


                for( int y=column ; y<(column+3) ; ++y )
                {

                    if( 0 != matrix[ x ][ y ] )
                    {
                        smallBlockArray[ count++ ] = matrix[ x ][ y ] ;
                    }
                    else {
                        toExit = true ;
                        break ;
                    }
                }
            }

            if( 9 == count )
            {
                BigBlock* bigBlock = new BigBlock( smallBlockArray ) ;
                bigBlockList->append( bigBlock );
            }
        }
    }

    return bigBlockList ;
}

void Sudoku::print( std::ostream& out )
{
    for( int row=0 ; row<(xMax*3) ; ++row )
    {

        if( 0 == row%3 && 0 != row )
        {
            for( int i=0 ; i<(yMax*8+1) ; ++i )
                out << "-" ;
            out << std::endl ;
        }



        for( int column=0 ; column<(yMax*3) ; ++column )
        {


            SmallBlock* block = matrix[ row/3 ][ column/3 ] ;
            char ch ;
            if( 0 !=block )
            {
                ch = 816 + block->getElement( row%3, column%3 ) ;
            }
            else
            {
                ch = '-' ;
            }

            if( 0 == column%3 )
                out << "| " ;
            out << ch << ' ' ;
        }
        out << '|' << std::endl ;
    }
}
void Sudoku::printFull( ostream& out )
{
    foreach( BigBlock* bigBlock, bigBlockList )
    {
        bigBlock->print( out ) ;
    }
}

//Check sudoku on rules
bool Sudoku::checkSudoku()
{
    bool allIsGood = true ;
    foreach( BigBlock* bigBlock, bigBlockList )
    {
        allIsGood = bigBlock->checkRules() ;
        if( !allIsGood )
            break ;
    }
    return allIsGood ;
}

bool Sudoku::allIsFilled()
{
    foreach( BigBlock* bigBlock, bigBlockList )
    {
        if( false == bigBlock->allIsFilled() )
            return false ;
    }
    return true ;
}

//Check for the
bool Sudoku::assumptionsIs()
{
    foreach( BigBlock* bigBlock, bigBlockList )
    {
        if( true == bigBlock->assumptionsIs() )
            return true ;
    }
    return false ;
}


//Clear all elements of last transaction
void Sudoku::clearLastTransaction()
{
    int idToDelete = partitionVector.back()->getTransactionID() ;
    while( !partitionVector.empty() )
    {
        PartitionElement *element = partitionVector.back() ;
        if( element->getTransactionID() == idToDelete )
        {
            element->revert() ;
            delete partitionVector.back() ;
            partitionVector.pop_back() ;
        }
        else
            break ;
    }
}
